// Copyright (c) 2020 Presto Labs Pte. Ltd.
// Author: linchuan

#include "coin2/exchange/bitflyer_futures/api_order/native_private_client.h"

#include <memory>
#include <string>

#include "coin2/exchange/base/api_util/url_params_builder.h"
#include "coin2/exchange/base/order/order.h"
#include "coin2/exchange/base/order/rate_config.h"
#include "coin2/exchange/bitflyer_futures/api_util/rate_tracker.h"
#include "coin2/exchange/bitflyer_futures/symbology/product.h"

auto BitflyerFuturesNativePrivateClient::GetRestOrderParams(
    const OrderSpec& order_spec,
    int64_t proc_order_id) -> RestParams {
  auto params = RestParams();

  using coin2::exchange::bitflyer_futures::symbology::BitflyerFuturesProduct;
  auto product = BitflyerFuturesProduct::FromStr(order_spec.product, order_spec.order_created_time);
  auto product_holder = GetProductHolder(product, order_spec.order_created_time);
  auto formatter = product_holder->product_info().order_formatter();
  params.set_product_code(order_spec.product);
  params.set_price(formatter.FormatPrice(order_spec.price));
  params.set_size(formatter.FormatQty(order_spec.qty));

  if (order_spec.order_type == OrderType::MARKET_ORDER) {
    params.set_child_order_type("MARKET");
  } else {
    params.set_child_order_type("LIMIT");
  }

  if (order_spec.order_side == OrderSide::BUY_ORDER) {
    params.set_side("BUY");
  } else if (order_spec.order_side == OrderSide::SELL_ORDER) {
    params.set_side("SELL");
  } else {
    NOTREACHED() << "Unknown order side: " << static_cast<int>(order_spec.order_side);
  }

  if (order_spec.duration == coin::proto::OrderDuration::FOK_ORDER) {
    params.set_time_in_force("FOK");
  } else if (order_spec.duration == coin::proto::OrderDuration::IOC_ORDER) {
    params.set_time_in_force("IOC");
  } else {
    params.set_time_in_force("GTC");
  }

  return params;
}

auto BitflyerFuturesNativePrivateClient::GetRestCancelParams(const IOrderContext& order_context)
    -> RestParams {
  auto params = RestParams();
  params.set_product_code(order_context.order_spec().product);
  params.set_child_order_acceptance_id(order_context.exchange_order_id());
  return params;
}

BitflyerFuturesNativePrivateClient::BitflyerFuturesNativePrivateClient(
    const MarketExchangeApi& mea,
    const NativeClientConfig& config_override) {
  mea_ = mea;
  NativeClientConfig config;
  config.set_rest_host("api.bitflyer.com");
  config.set_rest_host_port(443);
  config.MergeFrom(config_override);
  LOG(INFO) << "[BitflyerFuturesNativePrivateClient] config: " << config.DebugString();

  auths_.Construct<BitflyerRestAuth, NativeClientConfig>(config);

  // rate limit tracker
  rate_tracker_ = std::make_unique<BitflyerRateTracker>(mea_, config);
}

std::shared_ptr<AsioHttpContext> BitflyerFuturesNativePrivateClient::QueryBalance() {
  auto ts = GetCurrentTimestamp();
  if (!HasEnoughRateLimit(ts, ActionType::QUERY)) {
    DLOG(ERROR) << "Rate limit! QueryBalance not submit!";
    return {};
  }

  UrlParamsBuilder builder;
  auto* context = GetAuth()->CreateRequestByGetWithSignature("/v1/me/getcollateral", &builder);
  Push(1, ts, ActionType::QUERY);

  context->query_type = OrderSubTopicId::REST_ACCOUNT_BALANCE;
  return std::shared_ptr<AsioHttpContext>(context);
}

std::shared_ptr<AsioHttpContext> BitflyerFuturesNativePrivateClient::QuerySpotBalance() {
  auto ts = GetCurrentTimestamp();
  if (!HasEnoughRateLimit(ts, ActionType::QUERY)) {
    DLOG(ERROR) << "Rate limit! QuerySpotBalance not submit!";
    return {};
  }

  UrlParamsBuilder builder;
  auto* context = GetAuth()->CreateRequestByGetWithSignature("/v1/me/getbalance", &builder);
  Push(1, ts, ActionType::QUERY);

  context->query_type = OrderSubTopicId::REST_ACCOUNTS;
  return std::shared_ptr<AsioHttpContext>(context);
}

std::shared_ptr<AsioHttpContext> BitflyerFuturesNativePrivateClient::QueryPosition(
    const RestParams& params) {
  auto ts = GetCurrentTimestamp();
  if (!HasEnoughRateLimit(ts, ActionType::QUERY)) {
    DLOG(ERROR) << "Rate limit! QueryPosition not submit!";
    return {};
  }

  UrlParamsBuilder builder;
  builder.PutGet("product_code", params.product_code());
  auto* context = GetAuth()->CreateRequestByGetWithSignature("/v1/me/getpositions", &builder);
  Push(1, ts, ActionType::QUERY);

  context->query_type = OrderSubTopicId::REST_ACCOUNT_POSITION;
  return std::shared_ptr<AsioHttpContext>(context);
}

std::shared_ptr<AsioHttpContext> BitflyerFuturesNativePrivateClient::QueryOrders(
    const RestParams& params) {
  auto ts = GetCurrentTimestamp();
  if (!HasEnoughRateLimit(ts, ActionType::QUERY)) {
    DLOG(ERROR) << "Rate limit! QueryOrders not submit!";
    return {};
  }

  UrlParamsBuilder builder;
  builder.PutGet("product_code", params.product_code());
  builder.PutGet("count", params.count());
  builder.PutGet("child_order_state", params.child_order_state());
  auto* context = GetAuth()->CreateRequestByGetWithSignature("/v1/me/getchildorders", &builder);
  Push(1, ts, ActionType::QUERY);

  if (params.child_order_state().empty()) {
    context->query_type = OrderSubTopicId::REST_ALL_ORDERS;
  } else if (params.child_order_state() == "ACTIVE") {
    context->query_type = OrderSubTopicId::REST_OPEN_ORDERS;
  } else {
    CHECK(false);
  }
  return std::shared_ptr<AsioHttpContext>(context);
}

std::shared_ptr<AsioHttpContext> BitflyerFuturesNativePrivateClient::QueryFills(
    const RestParams& params) {
  auto ts = GetCurrentTimestamp();
  if (!HasEnoughRateLimit(ts, ActionType::QUERY)) {
    DLOG(ERROR) << "Rate limit! QueryFills not submit!";
    return {};
  }

  UrlParamsBuilder builder;
  builder.PutGet("product_code", params.product_code());
  builder.PutGet("count", params.count());
  auto* context = GetAuth()->CreateRequestByGetWithSignature("/v1/me/getexecutions", &builder);
  Push(1, ts, ActionType::QUERY);

  context->query_type = OrderSubTopicId::REST_FILL_ORDERS;
  return std::shared_ptr<AsioHttpContext>(context);
}

std::shared_ptr<AsioHttpContext> BitflyerFuturesNativePrivateClient::PlaceOrder(
    const RestParams& params) {
  auto ts = GetCurrentTimestamp();
  UrlParamsBuilder builder;
  builder.PutPost("product_code", params.product_code());
  builder.PutPost("side", params.side());
  builder.PutPost("price", params.price());
  builder.PutPost("size", params.size());
  builder.PutPost("child_order_type", params.child_order_type());
  builder.PutPost("time_in_force", params.time_in_force());
  auto* context = GetAuth()->CreateRequestByPostWithSignature("/v1/me/sendchildorder", &builder);
  Push(1, ts, ActionType::PLACE);

  context->query_type = OrderSubTopicId::SUBMIT_RESPONSE;
  return std::shared_ptr<AsioHttpContext>(context);
}

std::shared_ptr<AsioHttpContext> BitflyerFuturesNativePrivateClient::CancelSingleOrder(
    const RestParams& params) {
  auto ts = GetCurrentTimestamp();
  UrlParamsBuilder builder;
  builder.PutPost("product_code", params.product_code());
  builder.PutPost("child_order_acceptance_id", params.child_order_acceptance_id());
  auto* context = GetAuth()->CreateRequestByPostWithSignature("/v1/me/cancelchildorder", &builder);
  Push(1, ts, ActionType::CANCEL);

  context->query_type = OrderSubTopicId::CANCEL_RESPONSE;
  return std::shared_ptr<AsioHttpContext>(context);
}

std::shared_ptr<AsioHttpContext> BitflyerFuturesNativePrivateClient::PlaceOrderNorm(
    const OrderExecution& order_exe) {
  const auto& order_spec = *order_exe.order_spec;
  const auto proc_order_id = order_exe.proc_order_id;
  auto params = GetRestOrderParams(order_spec, proc_order_id);
  return PlaceOrder(params);
}

std::shared_ptr<AsioHttpContext> BitflyerFuturesNativePrivateClient::CancelOrderNorm(
    const OrderExecution& order_exe) {
  auto params = GetRestCancelParams(*order_exe.oc);
  return CancelSingleOrder(params);
}

std::shared_ptr<AsioHttpContext> BitflyerFuturesNativePrivateClient::SubmitOrderExecution(
    const OrderExecutionProto& order) {
  switch (order.type()) {
    case OrderExecutionProto::SUBMIT_ORDER: {
      auto order_spec = OrderSpec::FromProto(order.order_spec());
      auto params = GetRestOrderParams(*order_spec, order.proc_order_id());
      return PlaceOrder(params);
    }
    case OrderExecutionProto::CANCEL_ORDER: {
      auto order_spec = OrderSpec::FromProto(order.order_spec());
      auto params = GetRestOrderParams(*order_spec, order.proc_order_id());
      return CancelSingleOrder(params);
    }
    default: {
      return nullptr;
    }
  }
}

std::shared_ptr<AsioHttpContext> BitflyerFuturesNativePrivateClient::SubmitRestQuery(
    const RestQueryProto& query) {
  RestParams params;
  switch (query.type()) {
    case RestQueryProto::QUERY_ACCOUNT_BALANCE: {
      return QueryBalance();
    }
    case RestQueryProto::QUERY_ACCOUNT_POSITION: {
      params.set_product_code("FX_BTC_JPY");
      return QueryPosition(params);
    }
    case RestQueryProto::QUERY_OPEN_ORDERS: {
      params.set_child_order_state("ACTIVE");
      return QueryOrders(params);
    }
    default: {
      return nullptr;
    }
  }
}
